package online.flowerinsnow.minecraft.bukkit.manhunt.util;

import online.flowerinsnow.minecraft.bukkit.manhunt.core.PlayerTeam;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import online.flowerinsnow.minecraft.bukkit.manhunt.Manhunt;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public final class VersionUtils {
    private VersionUtils() {
    }

    public static final String VERSION = Bukkit.getServer().getClass().getName().replace("org.bukkit.craftbukkit.", "").replace(".CraftServer", "");

    public static class CBClasses {
        public static final Class<?> CRAFT_ITEM_STACK = getCBClass("inventory.CraftItemStack");
    }

    public static class CBMethods {
        /**
         * org.bukkit.craftbukkit.$VERSION.inventory.CraftItemStack#asNMSCopy#asNMSCopy(org.bukkit.inventory.ItemStack)
         */
        public static final Method CRAFT_ITEM_STACK__AS_NMS_COPY = getMethod(CBClasses.CRAFT_ITEM_STACK, new Class[]{ItemStack.class}, "asNMSCopy");

        /**
         * org.bukkit.craftbukkit.$VERSION.inventory.CraftItemStack#asBukkitCopy(net.minecraft.(server.$VERSION|world.item).ItemStack)
         */
        public static final Method CRAFT_ITEM_STACK__AS_BUKKIT_COPY = getMethod(CBClasses.CRAFT_ITEM_STACK, new Class[]{NMSClasses.ITEM_STACK}, "asBukkitCopy");
    }

    public static class NMSClasses {
        /**
         * net.minecraft.server.$VERSION.ItemStack (1.16)
         * net.minecraft.world.item.ItemStack (1.17+)
         */
        public static final Class<?> ITEM_STACK = getNMSClass("world.item.ItemStack");

        /**
         * net.minecraft.server.$VERSION.NBTTagCompound (1.16)
         * net.minecraft.nbt.NBTTagCompound (1.17+)
         */
        public static final Class<?> NBT_TAG_COMPOUND = getNMSClass("nbt.NBTTagCompound");
    }

    public static class NMSMethods {
        /**
         * net.minecraft.(server.$VERSION|world.item).ItemStack#getOrCreateTag()
         */
        public static final Method ITEM_STACK__GET_OR_CREATE_TAG = getMethod(NMSClasses.ITEM_STACK, new Class[0], "getOrCreateTag", "t");

        /**
         * net.minecraft.(server.$VERSION|nbt).NBTTagCompound#setString(java.lang.String, java.lang.String)
         */
        public static final Method NBT_TAG_COMPOUND__SET_STRING = getMethod(NMSClasses.NBT_TAG_COMPOUND, new Class[]{String.class, String.class}, "setString", "a");

        /**
         * net.minecraft.(server.$VERSION|nbt).NBTTagCompound#setBoolean(java.lang.String, java.lang.String)
         */
        public static final Method NBT_TAG_COMPOUND__SET_BOOLEAN = getMethod(NMSClasses.NBT_TAG_COMPOUND, new Class[]{String.class, Boolean.TYPE}, "setBoolean", "a");

        /**
         * net.minecraft.(server.$VERSION|world.item).ItemStack#setTag(NBTTagCompound)
         */
        public static final Method ITEM_STACK__SET_TAG = getMethod(NMSClasses.ITEM_STACK, new Class[]{NMSClasses.NBT_TAG_COMPOUND}, "setTag", "c");

        /**
         * net.minecraft.(server.$VERSION|nbt).NBTTagCompound#getString(java.lang.String)
         */
        public static final Method NBT_TAG_COMPOUND__GET_STRING = getMethod(NMSClasses.NBT_TAG_COMPOUND, new Class[]{String.class}, "getString", "l");

        /**
         * net.minecraft.(server.$VERSION|nbt).NBTTagCompound#getBoolean(java.lang.String)
         */
        public static final Method NBT_TAG_COMPOUND__GET_BOOLEAN = getMethod(NMSClasses.NBT_TAG_COMPOUND, new Class[]{String.class}, "getBoolean", "q");
    }

    @SuppressWarnings("ConstantConditions")
    public static ItemStack getSkullOf(Player player) {
        ItemStack skull = new ItemStack(Material.PLAYER_HEAD);
        SkullMeta meta = (SkullMeta) skull.getItemMeta();
        meta.setOwningPlayer(player);
        skull.setItemMeta(meta);

        try {
            Object stack = CBMethods.CRAFT_ITEM_STACK__AS_NMS_COPY.invoke(null, skull);
            Object tag = NMSMethods.ITEM_STACK__GET_OR_CREATE_TAG.invoke(stack);

            PlayerTeam team = Manhunt.getTeamManager().getPlayerTeam(player);
            if (team == PlayerTeam.HUNTER || team == PlayerTeam.RUNNER) {
                NMSMethods.NBT_TAG_COMPOUND__SET_STRING.invoke(tag, "team", team.name());
                NMSMethods.NBT_TAG_COMPOUND__SET_BOOLEAN.invoke(tag, "eatable", true);
            }

            NMSMethods.ITEM_STACK__SET_TAG.invoke(stack, tag);
            return (ItemStack) CBMethods.CRAFT_ITEM_STACK__AS_BUKKIT_COPY.invoke(null, stack);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new IllegalStateException(e);
        }
    }

    @SuppressWarnings("ConstantConditions")
    public static Object getNMSItemStack(ItemStack item) {
        try {
            return CBMethods.CRAFT_ITEM_STACK__AS_NMS_COPY.invoke(null, item);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new IllegalStateException(e);
        }
    }

    @SuppressWarnings("ConstantConditions")
    public static ItemStack getBukkitItemStack(Object nmsItem) {
        try {
            return (ItemStack) CBMethods.CRAFT_ITEM_STACK__AS_BUKKIT_COPY.invoke(null, nmsItem);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new IllegalStateException(e);
        }
    }

//    public static ItemStack getSkullOf(Player player) {
//        ItemStack skull = new ItemStack(Material.PLAYER_HEAD);
//        SkullMeta meta = (SkullMeta) skull.getItemMeta();
//        //noinspection ConstantConditions
//        meta.setOwningPlayer(player);
//        skull.setItemMeta(meta);
//
//        net.minecraft.server.v1_16_R3.ItemStack stack = CraftItemStack.asNMSCopy(skull);
//        NBTTagCompound tag = stack.getOrCreateTag();
//
//        PlayerTeam team = Manhunt.getTeamManager().getPlayerTeam(player);
//        if (team == PlayerTeam.HUNTER || team == PlayerTeam.RUNNER) {
//            tag.setString("team", team.name());
//            tag.setBoolean("eatable", true);
//        }
//        stack.setTag(tag);
//        return CraftItemStack.asBukkitCopy(stack);
//    }

    public static Class<?> getClass(String... possibleNames) {
        for (String possibleName : possibleNames) {
            try {
                return Class.forName(possibleName);
            } catch (ClassNotFoundException ignored) {
            }
        }
        return null;
    }

    public static Field getField(Class<?> cls, String... possibleNames) {
        for (String possibleName : possibleNames) {
            try {
                Field field = cls.getDeclaredField(possibleName);
                field.setAccessible(true);
                return field;
            } catch (NoSuchFieldException ignored) {
            }
        }
        return null;
    }

    public static Method getMethod(Class<?> cls, Class<?>[] params, String... possibleNames) {
        for (String possibleName : possibleNames) {
            try {
                Method method = cls.getDeclaredMethod(possibleName, params);
                method.setAccessible(true);
                return method;
            } catch (NoSuchMethodException ignored) {
            }
        }
        return null;
    }

    public static Class<?> getNMSClass(String name) {
        if (is1_16()) {
            String[] split = name.split("\\.");
            try {
                return Class.forName("net.minecraft.server." + VERSION + "." + split[split.length - 1]);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                return null;
            }
        } else {
            try {
                return Class.forName("net.minecraft." + name);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                return null;
            }
        }
    }

    public static Class<?> getCBClass(String name) {
        try {
            return Class.forName("org.bukkit.craftbukkit." + VERSION + "." + name);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static boolean is1_16() {
        return VERSION.startsWith("v1_16");
    }
}
